package io.web.gateway.filter;

import cn.hutool.json.JSONUtil;
import io.web.gateway.rpc.AdminService;
import io.web.gateway.utils.ResponseHandler;
import io.web.utils.R;
import io.web.utils.R;
import io.web.vo.interfaceinfo.InterfaceInfo;
import io.web.vo.interfaceinfo.InterfaceInfoQueryRequest;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

/**
 * 访问api接口的filter
 * 主要功能：
 * 1.打印request和response日志.
 * 2.判断api接口是否存在
 * 3.调用接口，打印响应，如果成功后，将调用次数增加
 *
 * @date 2023-05-30
 */
@Component
@Slf4j
@AllArgsConstructor
public class ApiGlobalFilter implements GlobalFilter, Ordered {

    private final AdminService adminService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ApiInfo apiInfo = logRequest(exchange);
        // api 接口判断
        InterfaceInfoQueryRequest infoQueryRequest = new InterfaceInfoQueryRequest();
        infoQueryRequest.setUrl(apiInfo.getUri());
        infoQueryRequest.setRequestMethod(apiInfo.getMethod());
        InterfaceInfo interfaceInfo = adminService.findInterfaceInfo(infoQueryRequest);
        log.info("infoQueryRequest = {}, interfaceInfo = {}", infoQueryRequest, interfaceInfo);
        if (Objects.isNull(interfaceInfo) || interfaceInfo.getId() == null || interfaceInfo.getUserId() == null) {
            return ResponseHandler.forbidden(exchange);
        }

        return logResponse(exchange, chain, interfaceInfo.getId(), interfaceInfo.getUserId());
    }

    private ApiInfo logRequest(ServerWebExchange exchange) {
        Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);

        ServerHttpRequest request = exchange.getRequest();
        log.info("请求唯一标识：" + request.getId());

        String uri = Objects.requireNonNull(route).getUri().toString() + request.getPath().value();
        log.info("请求路径：" + uri);

        log.info("请求方法：" + request.getMethodValue());
        log.info("请求参数：" + request.getQueryParams());
        String sourceAddress = Objects.requireNonNull(request.getLocalAddress()).getHostName();
        log.info("请求来源地址：" + sourceAddress);
        log.info("请求来源地址：" + request.getRemoteAddress());
        return new ApiInfo(uri, request.getMethodValue());
    }

    public Mono<Void> logResponse(ServerWebExchange exchange, GatewayFilterChain chain, Long interfaceId, Long userId) {
        try {
            ServerHttpResponse response = exchange.getResponse();
            DataBufferFactory bufferFactory = response.bufferFactory();
            // 响应装饰
            ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (getStatusCode() != null && body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                        return super.writeWith(fluxBody.map(dataBuffer -> {
                            // 如果响应过大，会进行截断，出现乱码，看api DefaultDataBufferFactory
                            // 有个join方法可以合并所有的流，乱码的问题解决
                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                            byte[] content = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(content);
                            // 释放掉内存
                            DataBufferUtils.release(dataBuffer);
                            String result = new String(content, StandardCharsets.UTF_8);
                            log.info("response code = {}, content = {}", response.getStatusCode(), result);
                            R r = JSONUtil.toBean(result, R.class);
                            if (r.getCode() == 0) {
                                apiPostHandler(interfaceId, userId);
                            }

                            return bufferFactory.wrap(content);
                        }));
                    }
                    // if body is not a flux. never got there.
                    return super.writeWith(body);
                }
            };
            // replace response with decorator
            return chain.filter(exchange.mutate().response(decoratedResponse).build());
        } catch (Exception e) {
            log.error("网关处理响应异常" + e);
            return chain.filter(exchange);
        }
    }

    /**
     * 调用成功后的后置处理
     *
     * @param interfaceId
     * @param userId
     */
    private void apiPostHandler(Long interfaceId, Long userId) {
        R<Boolean> res = adminService.updateTimes(userId, interfaceId);
        if (res.getCode() != 0 || !res.getData()) {
            //>> TODO Error?.
            throw new RuntimeException("fail to updateTimes");
        }
    }

    /**
     * 要设置的小一点，避免{@link NettyWriteResponseFilter} 先返回
     *
     * @return
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 300;
    }

    @Data
    @AllArgsConstructor
    static class ApiInfo {
        private String uri;
        private String method;
    }

}
